package bg.btanev.eightyfour.repository.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.Validate;
import org.hibernate.Criteria;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import bg.btanev.eightyfour.dao.GenericDAO;
import bg.btanev.eightyfour.dao.PagingConfig;
import bg.btanev.eightyfour.domain.Tag;
import bg.btanev.eightyfour.domain.Target;
import bg.btanev.eightyfour.domain.person.Person;
import bg.btanev.eightyfour.domain.profile.Profile;
import bg.btanev.eightyfour.domain.profile.ProfileEvent;
import bg.btanev.eightyfour.domain.profile.feed.FeedProfile;
import bg.btanev.eightyfour.repository.ProfileRepository;
import bg.btanev.eightyfour.shared.state.ProfileType;

/**
 *
 * @author B.Tanev
 *
 */
public class ProfileRepositoryImpl implements ProfileRepository {
	/**
	 * Logger for this class
	 */
	private static final Logger logger = LoggerFactory.getLogger(ProfileRepositoryImpl.class);

	private GenericDAO<Long, FeedProfile> feedProfileDAO;
	private GenericDAO<Long, Profile<ProfileEvent>> profileDAO;
	private GenericDAO<Long, ProfileEvent> profileEventDAO;

	public ProfileRepositoryImpl() {
	}

	public void setFeedProfileDAO(GenericDAO<Long, FeedProfile> feedProfileDAO) {
		this.feedProfileDAO = feedProfileDAO;
	}

	public void setProfileDAO(GenericDAO<Long, Profile<ProfileEvent>> profileDAO) {
		this.profileDAO = profileDAO;
	}

	public void setProfileEventDAO(GenericDAO<Long, ProfileEvent> profileEventDAO) {
		this.profileEventDAO = profileEventDAO;
	}

	@SuppressWarnings("unchecked")
	@Override
	public FeedProfile createAndStoreFeedProfile(String name, ProfileType type, Target target, String feedUrl) {
		logger.debug("createAndStoreFeedProfile(String, Target, String) - start"); //$NON-NLS-1$

		FeedProfile feedProfile = new FeedProfile(name, type, target, feedUrl);
		storeProfile((Profile) feedProfile);

		logger.debug("createAndStoreFeedProfile(String, Target, String) - end"); //$NON-NLS-1$
		return feedProfile;
	}

	@Override
	public List<Profile<ProfileEvent>> findAllProfiles(Target target) {
		logger.debug("findAllProfiles(Target) - start"); //$NON-NLS-1$

		Validate.notNull(target);

		DetachedCriteria detachedCriteria = profileDAO.createDetachedCriteria();
		detachedCriteria.add(Restrictions.eq("target", target));
		detachedCriteria.addOrder(Order.asc("id"));

		List<Profile<ProfileEvent>> result = profileDAO.findByCriteria(detachedCriteria);

		logger.debug("findAllProfiles(Target) - end"); //$NON-NLS-1$
		return result;
	}

	@Override
	public List<Profile<ProfileEvent>> findAllProfiles(Person person) {
		Validate.notNull(person);
		return findAllProfiles(person.getTarget());
	}

	@Override
	public void removeProfile(Profile<ProfileEvent> profile) {
		profileDAO.delete(profile);
	}

	@Override
	public void storeProfile(Profile<ProfileEvent> profile) {
		profileDAO.saveUpdateEntity(profile);
	}

	@Override
	public List<ProfileEvent> findProfileEventsForTarget(Target target) {
		return findProfileEventsForTarget(target, null);
	}

	@Override
	public List<ProfileEvent> findProfileEventsForTarget(Target target, PagingConfig pagingConfig) {
		logger.debug("findProfileEventsForTarget(Target) - start"); //$NON-NLS-1$

		Validate.notNull(target);

		DetachedCriteria detachedCriteria = createFindProfileEventForTargetCriteria(target);
		List<ProfileEvent> returnList = profileEventDAO.findByCriteria(detachedCriteria, pagingConfig);

		logger.debug("findProfileEventsForTarget(Target) - end"); //$NON-NLS-1$
		return returnList;
	}

	private DetachedCriteria createFindProfileEventForTargetCriteria(Target target) {
		assert (target != null);

		DetachedCriteria detachedCriteria = profileEventDAO.createDetachedCriteria();

		detachedCriteria.createAlias("profile", "pe", Criteria.LEFT_JOIN);
		detachedCriteria.add(Restrictions.eq("pe.target", target));

		return detachedCriteria;
	}

	@Override
	public List<ProfileEvent> findProfileEventsForTargetAndKeywork(Target target, String keyword) {
		logger.debug("findProfileEventsForTargetAndKeywork(Target, String) - start"); //$NON-NLS-1$

		Validate.notNull(target);
		Validate.notEmpty(keyword);

		DetachedCriteria detachedCriteria = createFindProfileEventForTargetCriteria(target);
		Criterion keyworkdCriterion = createKeywordCriterion(keyword);
		detachedCriteria.add(keyworkdCriterion);

		List<ProfileEvent> returnList = profileEventDAO.findByCriteria(detachedCriteria);

		logger.debug("findProfileEventsForTargetAndKeywork(Target, String) - end"); //$NON-NLS-1$
		return returnList;
	}

	private Criterion createKeywordCriterion(String keyword) {
		assert (keyword != null && keyword.length() > 0);

		Criterion keyworkdCriterion = Restrictions.or(Restrictions.ilike("title", keyword, MatchMode.ANYWHERE),
				Restrictions.ilike("description", keyword, MatchMode.ANYWHERE));

		return keyworkdCriterion;
	}

	@Override
	public List<ProfileEvent> findProfileEventsForTargetAndTag(Target target, Tag tag) {
		logger.debug("findProfileEventsForTargetAndTag(Target, Tag) - start"); //$NON-NLS-1$

		Validate.notNull(target);
		Validate.notNull(tag);

		DetachedCriteria detachedCriteria = createFindProfileEventForTargetCriteria(target);
		detachedCriteria.createCriteria("tags").add(Restrictions.idEq(tag.getId()));

		List<ProfileEvent> returnList = profileEventDAO.findByCriteria(detachedCriteria);

		logger.debug("findProfileEventsForTargetAndTag(Target, Tag) - end"); //$NON-NLS-1$
		return returnList;
	}

	@Override
	public Profile<ProfileEvent> findByIdentifier(Long id) {
		logger.debug("findByIdentofier(Long) - start"); //$NON-NLS-1$

		Profile<ProfileEvent> returnProfile = profileDAO.findById(id);

		logger.debug("findByIdentofier(Long) - end"); //$NON-NLS-1$
		return returnProfile;
	}

	@Override
	public List<ProfileEvent> findProfileEventsForTarget(Target target, Date beginDate, Date endDate,
			ProfileType profileType, List<Tag> tags, Set<String> searchExpressions, PagingConfig pagingConfig) {

		logger.debug("findProfileEventsForTarget(Target, Date, Date, List<Tag>, Set<String>, PagingConfig) - start"); //$NON-NLS-1$

		Validate.notNull(target);
		DetachedCriteria detachedCriteria = createFindProfileEventForTargetCriteria(target);

		// appending date restrictions
		if (beginDate != null && endDate != null) {
			Validate.isTrue(beginDate.before(endDate));
		}

		if (beginDate != null) {
			detachedCriteria.add(Restrictions.ge("date", beginDate));

			logger
					.debug("findProfileEventsForTarget(Target, Date, Date, List<Tag>, Set<String>, PagingConfig) - beginDate restriction added: " + beginDate); //$NON-NLS-1$
		}

		if (endDate != null) {
			detachedCriteria.add(Restrictions.lt("date", endDate));

			logger
					.debug("findProfileEventsForTarget(Target, Date, Date, List<Tag>, Set<String>, PagingConfig) - endDate restriction added: " + endDate); //$NON-NLS-1$
		}

		// append profile type restriction
		if (profileType != null) {
			detachedCriteria.add(Restrictions.eq("pe.type", profileType));
			logger
					.debug("findProfileEventsForTarget(Target, Date, Date, List<Tag>, Set<String>, PagingConfig) - profile type restriction added: " + profileType); //$NON-NLS-1$
		}

		// appending tags restrictions
		if (tags != null && tags.size() > 0) {
			Set<String> tagNames = new HashSet<String>(tags.size());
			for (Tag tag : tags) {
				tagNames.add(tag.getId());
			}
			detachedCriteria.createCriteria("tags").add(Restrictions.in("id", tagNames));

			logger
					.debug("findProfileEventsForTarget(Target, Date, Date, List<Tag>, Set<String>, PagingConfig) - tag restriction added"); //$NON-NLS-1$
		}

		// appending search expression restrictions
		if (searchExpressions != null && searchExpressions.size() > 0) {
			List<Criterion> expressionCriterions = new ArrayList<Criterion>(searchExpressions.size());

			for (String expression : searchExpressions) {
				expressionCriterions.add(createKeywordCriterion(expression));
			}

			Criterion searchExpressionsCriterion = expressionCriterions.get(0);
			for (int i = 1; i < expressionCriterions.size(); i++) {
				searchExpressionsCriterion = Restrictions.or(searchExpressionsCriterion, expressionCriterions.get(i));
			}

			detachedCriteria.add(searchExpressionsCriterion);
			logger
					.debug("findProfileEventsForTarget(Target, Date, Date, List<Tag>, Set<String>, PagingConfig) - search expressions restriction added"); //$NON-NLS-1$
		}

		List<ProfileEvent> returnList = profileEventDAO.findByCriteria(detachedCriteria, pagingConfig);

		logger.debug("findProfileEventsForTarget(Target, Date, Date, List<Tag>, Set<String>, PagingConfig) - end"); //$NON-NLS-1$
		return returnList;
	}

	@Override
	public List<ProfileEvent> findProfileEventsForTarget(Target target, Date beginDate, Date endDate,
			ProfileType profileType, List<Tag> tags, Set<String> searchExpressions) {

		return findProfileEventsForTarget(target, beginDate, endDate, profileType, tags, searchExpressions, null);
	}
}
